#include	"Window.h"
#include	"WindowsException.h"
#include	<Configuration/Config.h>
#include	<gl/gl.h>
#include	<gl/glu.h>

namespace
{

	std::string GenerateSafeClassName(const std::string & windowTitle)
	{
		std::string result = windowTitle;
		for(std::string::iterator it = result.begin(); it != result.end(); ++it)
		{
			if((*it == ' ') || (*it == '\\') || (*it == '/'))
			{
				*it = '_';
			}
		}
		result += "_Window_Class";
		return result;
	}

}

Window::Window(const std::string & title, HINSTANCE instance, const Config & config)
	:	m_title()
	,	m_fullScreen(false)
	,	m_size(0, 0)
	,	m_clientSize(0, 0)
	,	m_instance(0)
	,	m_windowClass(0)
	,	m_windowHandle(0)
	,	m_deviceContext(0)
	,	m_renderingContext(0)
	,	m_gotCloseMessage(false)
	,	m_windowActive(false)
	,	m_cursor(0, 0)
{
	Initialize(title, instance, config);
}

Window::~Window()
{
	Cleanup();
}

const Vector2i & Window::Size() const
{
	return m_clientSize;
}

HWND Window::WindowHandle() const
{
	return m_windowHandle;
}

HDC Window::DeviceContext() const
{
	return m_deviceContext;
}

HGLRC Window::RenderingContext() const
{
	return m_renderingContext;
}

bool Window::IsWindowClosing() const
{
	return m_gotCloseMessage;
}

bool Window::IsWindowActive() const
{
	return m_windowActive;
}

void Window::SwapBuffers()
{
	::SwapBuffers(m_deviceContext);
}

const Vector2i & Window::MouseCursor() const
{
	return m_cursor;
}

void Window::Initialize(const std::string & title, HINSTANCE instance, const Config & config)
{
	m_title = title;
	m_fullScreen = !config.window.windowed;
	m_size.Set(config.window.width, config.window.height);
	m_instance = instance;
	RegisterWindowClass(GenerateSafeClassName(m_title));
	Create(config.window.bit_depth);
	InitializeContexts(config.window.bit_depth);
	ActivateRenderingContext(true);
//	ShowWindow(true);
	ResizeWindow();
	InitializeOpenGL();
}

void Window::Cleanup()
{
//	ShowWindow(false);
	ActivateRenderingContext(false);
	CleanupContexts();
	Destroy();
	UnregisterWindowClass();
}

LRESULT Window::WindowProcedure(UINT message, WPARAM wParam, LPARAM lParam)
{
	LRESULT result = 0;
	switch(message)
	{
	case WM_SIZE:
		{
			switch(wParam)
			{
			case SIZE_MAXHIDE:
			case SIZE_MAXSHOW:
			case SIZE_MINIMIZED:
				{
					m_windowActive = false;
				}
				break;
			case SIZE_RESTORED:
			case SIZE_MAXIMIZED:
				{
					m_windowActive = true;
					ResizeWindow();
				}
				break;
			}
		}
		break;
	case WM_ACTIVATE:
		{
			m_windowActive = (WA_INACTIVE != LOWORD(wParam));
		}
		break;
	case WM_CLOSE:
		{
			m_gotCloseMessage = true;
		}
		break;
	case WM_MOUSEMOVE:
		{
			m_cursor.Set(LOWORD(lParam), HIWORD(lParam));
		}
		break;
	default:
		result = ::DefWindowProc(m_windowHandle, message, wParam, lParam);
	}
	return result;
}

LRESULT CALLBACK Window::MainWindowProcedure(HWND handle, UINT message, WPARAM wParam, LPARAM lParam)
{
	LRESULT result = 0;
	Window * windowObject = 0;
	switch(message)
	{
	case WM_CREATE:
		{
			CREATESTRUCT * createStructure = reinterpret_cast< CREATESTRUCT * >(lParam);
			windowObject = reinterpret_cast< Window * >(createStructure->lpCreateParams);
			windowObject->m_windowHandle = handle;
			::SetWindowLongPtr(handle, GWLP_USERDATA, reinterpret_cast< LONG_PTR >(windowObject));
			result = windowObject->WindowProcedure(message, wParam, lParam);
		}
		break;
	case WM_DESTROY:
		{
			windowObject = reinterpret_cast< Window * >(::GetWindowLongPtr(handle, GWLP_USERDATA));
			if(0 != windowObject)
			{
				result = windowObject->WindowProcedure(message, wParam, lParam);
			}
			::PostQuitMessage(0);
		}
		break;
	default:
		{
			windowObject = reinterpret_cast< Window * >(::GetWindowLongPtr(handle, GWLP_USERDATA));
			if(0 != windowObject)
			{
				result = windowObject->WindowProcedure(message, wParam, lParam);
			}
			else
			{
				result = ::DefWindowProc(handle, message, wParam, lParam);
			}
		}
	}
	return result;
}

void Window::TimeToQuit()
{
	::PostMessage(m_windowHandle, WM_CLOSE, 0, 0);
}

void Window::RegisterWindowClass(const std::string & className)
{
	WNDCLASSEX windowClassStruct;
	windowClassStruct.cbSize = sizeof(WNDCLASSEX);
	windowClassStruct.cbClsExtra = 0;
	windowClassStruct.cbWndExtra = sizeof(void*);
	windowClassStruct.hCursor = 0;
	windowClassStruct.hIcon = static_cast< HICON >(::LoadImage(m_instance, MAKEINTRESOURCE(101), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR));
	windowClassStruct.hIconSm = static_cast< HICON >(::LoadImage(m_instance, MAKEINTRESOURCE(101), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
	windowClassStruct.hbrBackground = 0;
	windowClassStruct.hInstance = m_instance;
	windowClassStruct.lpszMenuName = 0;
	windowClassStruct.lpszClassName = className.c_str();
	windowClassStruct.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	windowClassStruct.lpfnWndProc = Window::MainWindowProcedure;
	m_windowClass = ::RegisterClassEx(&windowClassStruct);
	if (0 == m_windowClass)
	{
		throw WindowsException(::GetLastError());
	}
}

void Window::UnregisterWindowClass()
{
	if(0 == ::UnregisterClass(reinterpret_cast<LPCTSTR>(m_windowClass), m_instance))
	{
		throw WindowsException(::GetLastError());
	}
	m_windowClass = 0;
}

void Window::Create(int bits)
{
	if(m_fullScreen)
	{
		DEVMODE mode;
		std::memset(&mode, 0, sizeof(DEVMODE));
		mode.dmSize = sizeof(DEVMODE);
		mode.dmPelsWidth = m_size[0];
		mode.dmPelsHeight = m_size[1];
		mode.dmBitsPerPel = bits;
		mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
		if(DISP_CHANGE_SUCCESSFUL != ::ChangeDisplaySettings(&mode, CDS_FULLSCREEN))
		{
			m_fullScreen = false;
		}
	}
	DWORD exStyle = (m_fullScreen) ? WS_EX_APPWINDOW : WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
	DWORD style = (m_fullScreen) ? WS_POPUP : WS_OVERLAPPEDWINDOW;
	::CreateWindowEx(exStyle, reinterpret_cast<LPCTSTR>(m_windowClass), m_title.c_str(), style, 0, 0, m_size[0], m_size[1], 0, 0, m_instance, this);
	if(0 == m_windowHandle)
	{
		throw WindowsException(::GetLastError());
	}
	if(m_fullScreen)
	{
		m_clientSize = m_size;
	}
	else
	{
		RECT r;
		if(0 == ::GetClientRect(m_windowHandle, &r))
		{
			throw WindowsException(::GetLastError());
		}
		m_clientSize[0] = r.right;
		m_clientSize[1] = r.bottom;
	}
}

void Window::Destroy()
{
	if(m_fullScreen)
	{
		ChangeDisplaySettings(0, 0);
	}
	if(0 == ::DestroyWindow(m_windowHandle))
	{
		throw WindowsException(::GetLastError());
	}
	m_windowHandle = 0;
}

void Window::InitializeContexts(int bits)
{
	m_deviceContext = ::GetDC(m_windowHandle);
	static PIXELFORMATDESCRIPTOR pixelFormatDescriptor =
	{
		sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_SWAP_EXCHANGE,							
		PFD_TYPE_RGBA, static_cast< BYTE >(bits), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0
	};
	int pixelFormat = ::ChoosePixelFormat(m_deviceContext, &pixelFormatDescriptor);
	if(0 == pixelFormat)
	{
		throw WindowsException(::GetLastError());
	}
	if(0 == ::SetPixelFormat(m_deviceContext, pixelFormat, &pixelFormatDescriptor))
	{
		throw WindowsException(::GetLastError());
	}
	m_renderingContext = ::wglCreateContext(m_deviceContext);
	if(0 == m_renderingContext)
	{
		throw WindowsException(::GetLastError());
	}
}

void Window::ActivateRenderingContext(bool activate)
{
	if(activate)
	{
		if(0 == ::wglMakeCurrent(m_deviceContext, m_renderingContext))
		{
			throw WindowsException(::GetLastError());
		}
	}
	else
	{
		if(0 == ::wglMakeCurrent(0, 0))
		{
			throw WindowsException(::GetLastError());
		}
	}
}

void Window::CleanupContexts()
{
	if(0 == ::wglDeleteContext(m_renderingContext))
	{
		throw WindowsException(::GetLastError());
	}
	m_renderingContext = 0;
	if(0 == ::ReleaseDC(m_windowHandle, m_deviceContext))
	{
		throw WindowsException(::GetLastError());
	}
	m_deviceContext = 0;
}

void Window::InitializeOpenGL()
{
	glShadeModel(GL_SMOOTH);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClearDepth(1.0f);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}

void Window::ResizeWindow()
{
	RECT r;
	if(0 == ::GetWindowRect(m_windowHandle, &r))
	{
		throw WindowsException(::GetLastError());
	}
	m_size.Set(r.right - r.left, r.bottom - r.top);
	if(m_fullScreen)
	{
		m_clientSize = m_size;
	}
	else
	{
		if(0 == ::GetClientRect(m_windowHandle, &r))
		{
			throw WindowsException(::GetLastError());
		}
		m_clientSize.Set(r.right, r.bottom);
	}
	glViewport(0, 0, m_clientSize[0], m_clientSize[1]);
}

void Window::ShowWindow(bool show)
{
	if(show)
	{
		::ShowWindow(m_windowHandle, SW_SHOW);
		::SetForegroundWindow(m_windowHandle);
		::SetFocus(m_windowHandle);
		::ShowCursor(FALSE);
	}
	else
	{
		::ShowCursor(TRUE);
		::ShowWindow(m_windowHandle, SW_HIDE);
	}
	m_windowActive = show;
}

